home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1993 July / InfoMagic USENET CD-ROM July 1993.ISO / sources / unix / volume25 / fido < prev    next >
Encoding:
Text File  |  1992-04-04  |  31.2 KB  |  1,276 lines

  1. Newsgroups: comp.sources.unix
  2. From: phil@Shiva.COM (Phil Budne)
  3. Subject: v25i163: fido - a new watchdog for users and machines
  4. Sender: unix-sources-moderator@pa.dec.com
  5. Approved: vixie@pa.dec.com
  6.  
  7. Submitted-By: phil@Shiva.COM (Phil Budne)
  8. Posting-Number: Volume 25, Issue 163
  9. Archive-Name: fido
  10.  
  11. [ This is an updated version of Volume 16, Issue 53.  It has various new
  12.   features which are described in the README file.  From the man page:
  13.  
  14.     SYNOPSIS
  15.          fido &
  16.  
  17.     FEATURES
  18.          Fido periodically scans the files created by rwhod(8c) and
  19.          reports the commings and goings of people and machines.
  20.          Fido reads a .fido init file to determine what to watch.
  21.          .fido is first checked for in the current directory, and
  22.          then in the directory contained in the HOME environment
  23.          variable.
  24.  
  25.   --vix ]
  26.  
  27. #! /bin/sh
  28. # This is a shell archive.  Remove anything before this line, then unpack
  29. # it by saving it into a file and typing "sh file".  To overwrite existing
  30. # files, type "sh file -c".  You can also feed this as standard input via
  31. # unshar, or by typing "sh <file", e.g..  If this archive is complete, you
  32. # will see the following message at the end:
  33. #        "End of archive 1 (of 1)."
  34. # Contents:  Makefile README fido.1 fido.c
  35. # Wrapped by phil@Shiva.COM on Mon Mar  2 13:14:00 1992
  36. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  37. if test -f Makefile -a "${1}" != "-c" ; then 
  38.   echo shar: Will not over-write existing file \"Makefile\"
  39. else
  40. echo shar: Extracting \"Makefile\" \(387 characters\)
  41. sed "s/^X//" >Makefile <<'END_OF_Makefile'
  42. X# Makefile for fido.
  43. X# typing "make DEBUG=n" builds a fido of debug level n
  44. X#
  45. X# GNU's fantastic CC
  46. XCC=gcc
  47. XDFLAGS=-g
  48. XCFLAGS=-O
  49. XDEBUG=4
  50. X
  51. Xfido:    fido.c
  52. X    $(CC) $(CFLAGS) -o fido fido.c
  53. X
  54. Xdfido$(DEBUG):    fido.c
  55. X    $(CC) $(DFLAGS) -DDEBUG=$(DEBUG) -o dfido$(DEBUG) fido.c
  56. X
  57. Xshar:    README Makefile fido.c fido.1
  58. X    makekit README Makefile fido.c fido.1
  59. X
  60. Xclean:
  61. X    rm -f Part* *~ core *.o fido dfido[0-9]*
  62. END_OF_Makefile
  63. if test 387 -ne `wc -c <Makefile`; then
  64.     echo shar: \"Makefile\" unpacked with wrong size!
  65. fi
  66. # end of overwriting check
  67. fi
  68. if test -f README -a "${1}" != "-c" ; then 
  69.   echo shar: Will not over-write existing file \"README\"
  70. else
  71. echo shar: Extracting \"README\" \(428 characters\)
  72. sed "s/^X//" >README <<'END_OF_README'
  73. XFido is a program I wrote in anticipation of getting a workstation.
  74. XI keep it running in my console window.  Recently it has become popular
  75. Xwith more users, and so I've done some cleanup and sent it out.
  76. X
  77. XSince I released fido (in August 1988) I added the "idle" and "line"
  78. Xoptions.
  79. X
  80. XI don't doubt that it is a great waste of CPU, but Hey, I like it.
  81. X
  82. X        Phil Budne <budd@bu-it.bu.edu>
  83. X        Boston University, Boston Ma
  84. X        June 1990
  85. END_OF_README
  86. if test 428 -ne `wc -c <README`; then
  87.     echo shar: \"README\" unpacked with wrong size!
  88. fi
  89. # end of overwriting check
  90. fi
  91. if test -f fido.1 -a "${1}" != "-c" ; then 
  92.   echo shar: Will not over-write existing file \"fido.1\"
  93. else
  94. echo shar: Extracting \"fido.1\" \(3275 characters\)
  95. sed "s/^X//" >fido.1 <<'END_OF_fido.1'
  96. X.\"-*-nroff-*-
  97. X.TH FIDO 1 "10 May 1989"
  98. X.SH NAME
  99. Xfido \- a watchdog for users and machines
  100. X.SH SYNOPSIS
  101. X.B fido &
  102. X.SH FEATURES
  103. X.I Fido
  104. Xperiodically scans the files created by
  105. X.IR rwhod (8c)
  106. Xand reports the commings and goings of people and machines.
  107. X.I Fido
  108. Xreads a
  109. X.I .fido
  110. Xinit file to determine what to watch. 
  111. X.I .fido
  112. Xis first checked for in the current directory, and then
  113. Xin the directory contained in the
  114. X.B \s-1HOME\s0
  115. Xenvironment variable.
  116. X.PP
  117. XThe
  118. X.I .fido
  119. Xfile consists of a list of commands from the list below.
  120. XLines starting with a hash-mark (#) are discarded as comments.
  121. X.HP
  122. X.B user
  123. X.IR user [@ host ]
  124. X.br
  125. XWatch for a specified user.  Normally only the first login and last
  126. Xlogout of the specified user will be reported.  If a host is
  127. Xspecified, then only instances of that user on that host are reported
  128. X(useful when multiple users have the same name).  You can have
  129. Xmultiple lines with the same username and different hosts (including
  130. Xthe form with no host).
  131. X.HP
  132. X.RB [ no ] beep
  133. X.br
  134. XRing bell on output (default is
  135. X.BR nobeep )
  136. X.HP
  137. X.RB [ no ] machines
  138. X.br
  139. XWatch machines go up and down (default is
  140. X.BR machines )
  141. X.HP
  142. X.B line
  143. X.IR terminal [@ host ]
  144. X.br
  145. XWatch for activity on a specific terminal line (of a specific host).
  146. X.HP
  147. X.B watch
  148. X.RI [ machine ]
  149. X.br
  150. XWatch
  151. X.I machine
  152. X(or all machines) and report when it is free. 
  153. X.HP
  154. X.RB [ no ] stamp
  155. X.br
  156. XOutput timestamps. (default is
  157. X.BR stamp )
  158. X.HP
  159. X.B idletime
  160. X.I minutes
  161. X.br
  162. XSpecify the 
  163. Xnumber of minutes until a user is declared idle (default is 15).
  164. X.HP
  165. X.B downtime
  166. X.I minutes
  167. X.br
  168. XSpecify the number of minutes before a host is declared down (default is 8).
  169. X.B Note!!
  170. XIf the
  171. X.IR rwho (1c)
  172. Xspool directory is located on another (NFS) host, a skew between
  173. Xtime of day clocks can cause major misery.
  174. X.HP
  175. X.B sleeptime
  176. X.I seconds
  177. X.br
  178. XSpecify number of seconds to sleep between scans (default is 60,
  179. Xminimum is 32).
  180. X.HP
  181. X.B tick
  182. X.I minutes
  183. X.br
  184. XIssue a (gratuitous) timestamp every
  185. X.I minutes
  186. Xminutes.  The timestamp will always be a multiple of
  187. X.I minutes
  188. Xminutes after the hour.
  189. X.PP
  190. XYou must specify at least one of
  191. X.BR user ,
  192. X.BR tick ,
  193. Xor
  194. X.B machines
  195. Xin your
  196. X.I .fido
  197. Xfile, otherwise
  198. X.I fido
  199. Xhas nothing to to!!
  200. XEach time it wakes up
  201. X.I fido
  202. Xchecks to see if it's parent pid is now pid 1 (init), if this
  203. Xis true, then
  204. X.IR fido 's
  205. Xorginal parent must have exited, and so
  206. X.I fido
  207. Xfollows its master into oblivion.
  208. X.SH FILES
  209. X.TP 20
  210. X.I /usr/spool/rwho
  211. Xrwho spool area
  212. X.PD 0
  213. X.TP
  214. X.I ./.fido
  215. Xfido init file first choice
  216. X.TP
  217. X.RI $\s-1HOME\s0 /.fido
  218. Xfido init file
  219. X.PD
  220. X.SH SEE ALSO
  221. X.IR who (1),
  222. X.IR rwho (1c),
  223. X.IR ruptime (1c),
  224. X.IR environ (5v),
  225. X.IR rwhod (8c),
  226. X.IR hungry (l)
  227. X.SH AUTHOR
  228. XPhilip L. Budne, Boston University, Distributed Systems Group
  229. X.SH BUGS
  230. XSee features.
  231. X.PP
  232. XNo way to exclude a given machine, or user on a given machine.
  233. X.PP
  234. XNo way to specify a particular terminal line.
  235. X.PP
  236. XNo way to specify any user on a given host (ie; watch for idle
  237. Xworkstations)
  238. X.PP
  239. X.I Fido
  240. Xwas inspired by a program of the same name running on the MIT
  241. XITS operating system for PDP-10s.
  242. X.PP
  243. X.I Fido
  244. Xcan only read 100 files from
  245. X.I /usr/spool/rwho
  246. X(see
  247. X.B \s-1NUMFILES\s0
  248. Xin the source)
  249. Xand the total number of bytes consumed by the filenames
  250. Xcannot exceed
  251. X.BR \s-1BPHOST\s0 "*" \s-1NUMFILES\s0 .
  252. X.RB ( \s-1BPHOST\s0
  253. Xis currently 32)
  254. END_OF_fido.1
  255. if test 3275 -ne `wc -c <fido.1`; then
  256.     echo shar: \"fido.1\" unpacked with wrong size!
  257. fi
  258. # end of overwriting check
  259. fi
  260. if test -f fido.c -a "${1}" != "-c" ; then 
  261.   echo shar: Will not over-write existing file \"fido.c\"
  262. else
  263. echo shar: Extracting \"fido.c\" \(23530 characters\)
  264. sed "s/^X//" >fido.c <<'END_OF_fido.c'
  265. X/*
  266. X *    Copyright 1988, 1989 Philip L. Budne.
  267. X *    All rights reserved.
  268. X *
  269. X *    This program may be freely distributed as long as this notice
  270. X *    and the above Copyright remain intact.
  271. X *
  272. X *    The author makes no claims of suitability for use.
  273. X */
  274. X
  275. X/*
  276. X *    Phil Budne @ BU/DSG
  277. X *
  278. X *    prehistoric    users list compiled into struct. machine
  279. X *            watching was a seperate program. ran on my 3b2.
  280. X *    8/1/86        read .fido file
  281. X *    1/87        check ppid
  282. X *    8/14/87        read rwho files directly (used to popen rwho!)
  283. X *    8/20/87        add beep again, add idletime
  284. X *            rework -- put found structure in target
  285. X *            report found users once -- now way to
  286. X *            track where someone is gone from. report idle.
  287. X *    8/21/87        add machines, hungry
  288. X *    4/19/88        look in HOME for .fido, quit if not found
  289. X *            stat WHODIR, keep list of files to avoid
  290. X *            re-reading spooldir constantly!
  291. X *    8/6/88        convert to open mhash. malloc targets.
  292. X *            add stomp. remove BEEP, STOMP, MT, HOST, AT
  293. X *            use static filenamestrings.
  294. X *    8/8/88!        keep boottime and sendtime in struct machine.
  295. X *            report reboots.
  296. X *    8/9/88        All dressed up. Ready to go!
  297. X *    3/7/89        Calculate hours in saydown!!
  298. X *            Show idle machines!! (watch)
  299. X *    5/10/89        Watch by terminal line (line). add output decorations.
  300. X *    10/24/90    Add tick
  301. X */
  302. X
  303. X/* do reverse video for machines? */
  304. X
  305. X# include <sys/types.h>
  306. X# include <sys/stat.h>
  307. X# ifdef NDIR
  308. X# include "ndir.h"
  309. X# else  /* NDIR not defined */
  310. X# include <sys/dir.h>
  311. X# endif /* NDIR not defined */
  312. X# include <strings.h>
  313. X# include <stdio.h>
  314. X# include <ctype.h>
  315. X# include <protocols/rwhod.h>        /* But not on 4.2 systems!! */
  316. X# include <time.h>
  317. X
  318. X# define EOS '\0'
  319. Xextern char *malloc();            /* from libc */
  320. X
  321. X# define bool int
  322. X# define TRUE 1
  323. X# define FALSE 0
  324. X
  325. X# define INITFILE ".fido"
  326. X
  327. X# define DEF_BEEP    FALSE
  328. X# define DEF_STAMP    TRUE
  329. X# define DEF_MACHINES    TRUE
  330. X# define DEF_DOWNTIME    8        /* minutes (less than 4 is noisy) */
  331. X# define DEF_IDLETIME    15        /* minutes */
  332. X
  333. X# define DEF_SLEEPTIME    60        /* seconds */
  334. X# define MIN_SLEEPTIME    32
  335. X
  336. X# define DEF_TICK    0        /* default timestamp (minutes) */
  337. X# define MAX_TICK    60        /* never allow tick over 1 hr */
  338. X
  339. X# define MAXBUF 132            /* various buffers */
  340. X
  341. X/* static lengths in target struct (fields from utmp) */
  342. X# define MAXHST 33
  343. X# define MAXUSR 9
  344. X# define MAXTTY 9
  345. X
  346. X# define NUMFILES 100        /* grrr -- a hardwired limit!! */
  347. X# define BPHOST 32        /* avg length for hostnames ick!! */
  348. X
  349. Xstruct target {
  350. X    enum { USER, LINE } type;
  351. X    char *host, *user;
  352. X    struct found {        /* count of entries found in each state */
  353. X    int nidle, idle, hungry;
  354. X    } found[2];            /* now and before */
  355. X    struct target *tnext;
  356. X};
  357. X# define TOTAL(tp,t) ((tp)->found[t].idle + (tp)->found[t].nidle)
  358. X
  359. Xstruct target *targets;        /* list of targets */
  360. Xstruct target *lasttarget;    /* last target created */
  361. Xint numt = 0;            /* number of targets */
  362. Xint this = 0;            /* toggling index (0/1) into found array */
  363. X
  364. Xchar wildhost[] = "*";
  365. X# define WILDHOST wildhost    /* empty host for targets */
  366. X
  367. Xint beep = DEF_BEEP;
  368. Xint stamp = DEF_STAMP;
  369. Xint machines = DEF_MACHINES;
  370. Xint downtime = DEF_DOWNTIME * 60;
  371. Xint idletime = DEF_IDLETIME * 60;
  372. Xint sleeptime = DEF_SLEEPTIME;
  373. Xint watchall = FALSE;
  374. Xint tick     = DEF_TICK;
  375. X
  376. Xchar nowstr[ 50 ];            /* for time stomping */
  377. Xtime_t now;                /* current time(3) */
  378. X
  379. Xstruct machine {            /* hash table of machines */
  380. X    char name[40];
  381. X    enum { UP=1, DOWN=2 } state;
  382. X    time_t latest;            /* last time seen up */
  383. X    int boottime, sendtime;        /* from whod packet */
  384. X    struct machine
  385. X    *hnext,                /* next in hash chain */
  386. X    *anext;                /* next in list of all hosts */
  387. X    int busy[2];            /* has non-idle users (now and then) */
  388. X    char watch;                /* watched flag */
  389. X};
  390. Xstruct machine *findmachine(), *newmachine(); /* forward */
  391. X
  392. X# define MHSIZE 101            /* small prime */
  393. Xstruct machine *mhash[ MHSIZE ], *allmachines;
  394. Xint firstpass = TRUE;
  395. X
  396. X# if DEBUG > 0
  397. Xchar *state_name[] = { "??", "UP", "DOWN" };
  398. X# endif /* DEBUG > 0 */
  399. X
  400. X# define WHODIR "/usr/spool/rwho"
  401. X
  402. X# define ISHUNGRY(we) \
  403. X    ((we->we_utmp.out_line[sizeof(we->we_utmp.out_line)-1] & 0200) != 0 )
  404. X
  405. X# define CLEARHUNGRY(we) \
  406. X    we->we_utmp.out_line[sizeof(we->we_utmp.out_line)-1] &= ~0200
  407. X
  408. Xtime_t
  409. Xgetnexttick( t, i )
  410. X    time_t t;
  411. X    int i;                /* interval in minutes <= 60 */
  412. X{
  413. X    struct tm *tm;
  414. X    int min;
  415. X
  416. X    tm = localtime(&t);
  417. X    min = i * ((tm->tm_min / i) + 1);    /* get next minute for tick */
  418. X    return( t + (min - tm->tm_min) * 60 - tm->tm_sec );
  419. X}
  420. X
  421. Xmain() {
  422. X    int i;
  423. X    time_t nextscan, nexttick;
  424. X
  425. X    if( !readinit( INITFILE ) ) {    /* check current dir */
  426. X    extern char *getenv();
  427. X    char tname[ 1024 ], *hp;
  428. X
  429. X    if( (hp = getenv("HOME")) == NULL ) { /* check 'home' */
  430. X        /* getpwuid(getuid()) */
  431. X        fprintf( stderr, "No ./.fido and HOME not set\n" );
  432. X        exit( 1 );
  433. X    } /* no HOME */
  434. X    sprintf( tname, "%s/%s", hp, INITFILE );
  435. X    if( !readinit( tname ) ) {
  436. X        fprintf( stderr, "No ./.fido or %s\n", tname );
  437. X        exit( 1 );
  438. X    } /* readinit failed */
  439. X    } /* no .fido in "." */
  440. X
  441. X# if DEBUG > 0
  442. X    dumpinit();
  443. X# endif /* DEBUG > 0 */
  444. X
  445. X    if( numt == 0 && !machines && tick == 0 ) {
  446. X    fprintf(stderr, "Nothing to do!!\n" );
  447. X    exit( 1 );
  448. X    } /* nothing to do!! */
  449. X
  450. X    if( chdir( WHODIR ) < 0 ) {
  451. X    perror( WHODIR );
  452. X    exit( 1 );
  453. X    } /* chdir failed */
  454. X
  455. X    nice( 10 );                /* be nice to real users */
  456. X
  457. X    sleep( 1 );                /* wait for shell to prompt */
  458. X    putchar('\n');            /* get fresh line */
  459. X
  460. X    time( &now );            /* get initial time */
  461. X    if( tick > 0 ) {
  462. X    nexttick = getnexttick( now, tick );
  463. X    }
  464. X    if( numt > 0 || machines )
  465. X    nextscan = now;            /* FIX? */
  466. X
  467. X    for( ; ; ) {
  468. X    int zzztime;
  469. X
  470. X    if( tick > 0 && now >= nexttick ) { /* time to tick?? */
  471. X        if( beep )
  472. X        putchar('\007');
  473. X        fputs( ctime( &now ), stdout );
  474. X        nexttick = getnexttick( now, tick );
  475. X    }
  476. X
  477. X    if( (numt > 0 || machines) && now >= nextscan ) {
  478. X        dowho();            /* prowl rwho spool dir */
  479. X        if( machines )        /* watching machines? */
  480. X        scan();            /* yell about them */
  481. X        this = !this;        /* toggle found index */
  482. X        nextscan = now + sleeptime;
  483. X    }
  484. X
  485. X    fflush( stdout );
  486. X
  487. X    if( tick == 0 )            /* no ticks */
  488. X        sleeptime = nextscan - now;    /* then only have scan to worry about */
  489. X    else {                /* ticking.. */
  490. X        if( numt > 0 || machines )    /* doing both */
  491. X        sleeptime = (nextscan < nexttick) ?
  492. X            nextscan - now : nexttick - now;
  493. X        else
  494. X        sleeptime = nexttick - now;
  495. X    } /* ticking */
  496. X
  497. X    sleep( sleeptime );        /* go to sleep */
  498. X    if( getppid() == 1 )        /* child of init? */
  499. X        exit( 1 );            /* quitting time */
  500. X    time( &now );
  501. X    } /* for ever */
  502. X} /* main */
  503. X
  504. Xdowho() {                /* poke thru rwho spool */
  505. X    register int i;
  506. X    register struct target *tp;
  507. X
  508. X    char **fp;
  509. X    struct whod wd;
  510. X    struct stat newstat;
  511. X    char buf[MAXBUF], user[MAXUSR], host[MAXHST], tty[MAXTTY];
  512. X
  513. X    static int nfiles;
  514. X    static struct stat oldstat;
  515. X    static char *filenames[ NUMFILES ];    /* ACK!! hardwired limits! */
  516. X    static char filenamestrings[ NUMFILES * BPHOST ]; /* avoid malloc/free */
  517. X
  518. X    if( stat( ".", &newstat ) < 0 )
  519. X    return;
  520. X
  521. X    if( newstat.st_mtime != oldstat.st_mtime ) {
  522. X    DIR *dirp;
  523. X    struct direct *d;
  524. X    register char *dp;
  525. X
  526. X    oldstat = newstat;
  527. X# if DEBUG > 0
  528. X    puts("(re)reading RWHODIR");
  529. X# endif /* DEBUG > 0 */
  530. X
  531. X    if( (dirp = opendir( "." )) == NULL )
  532. X        return;
  533. X
  534. X    nfiles = 0;
  535. X    fp = filenames;
  536. X    dp = filenamestrings;
  537. X    while( (d = readdir( dirp )) != NULL ) {
  538. X        register char *sp;
  539. X
  540. X        if( strncmp( d->d_name, "whod.", 5 ) != 0 )
  541. X        continue;
  542. X
  543. X# if DEBUG > 4
  544. X        printf("readdir: %s\n", d->d_name );
  545. X# endif /* DEBUG > 4 */
  546. X        if( nfiles == NUMFILES ) {
  547. X        fprintf( stderr, "Too many files!! Increase NUMFILES!!\n");
  548. X        break;
  549. X        } /* too many files */
  550. X
  551. X        nfiles++;
  552. X        *fp++ = dp;
  553. X        sp = d->d_name;
  554. X        while( *dp++ = *sp++ )
  555. X        ;
  556. X    } /* while readdir */
  557. X    closedir( dirp );
  558. X    } /* rwhod mtime has changed */
  559. X
  560. X    for( tp = targets, i = 0; i < numt; tp = tp->tnext, i++ ) {
  561. X    tp->found[this].hungry = 0;
  562. X    tp->found[this].nidle  = 0;
  563. X    tp->found[this].idle   = 0;
  564. X    } /* for i */
  565. X
  566. X    if( stamp ) {
  567. X    strcpy( nowstr, ctime( &now ) );
  568. X    nowstr[ 16 ] = ' ';
  569. X    nowstr[ 17 ] = EOS;        /* kill year and seconds */
  570. X    } /* stamping */
  571. X
  572. X    for( fp = filenames, i = 0; i < nfiles; i++, fp++ ) {
  573. X    register struct whoent *we;
  574. X    struct machine *mp;
  575. X    int f, cc, down;
  576. X    time_t recvtime;
  577. X    int try;
  578. X    
  579. X# ifdef USEFILETIME
  580. X    struct stat ftime;
  581. X# endif /* USEFILETIME defined */
  582. X
  583. X# if DEBUG > 19
  584. X    printf("=== %s ===\n", *fp );
  585. X# endif /* DEBUG > 19 */
  586. X
  587. X    for( try = 0; try < 5; try++, sleep(1) ) {
  588. X        cc = -1;
  589. X        if( (f = open( *fp, 0 )) < 0 )
  590. X        continue;        /* try to read again */
  591. X
  592. X        cc = read( f, &wd, sizeof( wd ));
  593. X# ifdef USEFILETIME
  594. X        fstat( f, &ftime );
  595. X# endif /* USEFILETIME defined */
  596. X        close( f );
  597. X
  598. X        cc -= sizeof( wd ) - sizeof ( wd.wd_we );
  599. X# if DEBUG > 19
  600. X        printf("%d chars\n", cc );
  601. X# endif /* DEBUG > 19 */
  602. X        if( cc < 0 ) {
  603. X# if DEBUG > 0 && DEBUG < 20
  604. X        printf("%s: %d chars\n", wd.wd_hostname, cc );
  605. X# endif /* DEBUG > 0 && DEBUG < 20 */
  606. X        continue;        /* re-read file */
  607. X        } /* short file */
  608. X        else
  609. X        break;
  610. X    } /* for each try */
  611. X    if( cc < 0 )
  612. X        continue;            /* re-read */
  613. X
  614. X# ifdef USEFILETIME
  615. X    recvtime = ftime.st_mtime;
  616. X# else  /* USEFILETIME not defined */
  617. X    recvtime = wd.wd_recvtime;
  618. X# endif /* USEFILETIME not defined */
  619. X
  620. X    down = now - recvtime;
  621. X# if DEBUG > 19
  622. X    printf( "down %d\n", down );
  623. X# endif /* DEBUG > 19 */
  624. X
  625. X    if( wd.wd_hostname[0] == EOS ) {
  626. X# if DEBUG > 0
  627. X        printf( "null hostname in file %s\n", *fp );
  628. X# endif /* DEBUG > 0 */
  629. X        continue;            /* re-read!? */
  630. X    } /* hostname empty */
  631. X
  632. X    if( machines ) {
  633. X        if( (mp = findmachine( wd.wd_hostname )) != NULL ) {
  634. X# if DEBUG > 10
  635. X        printf("found machine: %s\n", wd.wd_hostname);
  636. X# endif /* DEBUG > 10 */
  637. X
  638. X        /* filter out freaks */
  639. X        if( mp->boottime != 0 &&
  640. X           wd.wd_boottime - mp->boottime > 120 ) { 
  641. X            sayreboot( mp );
  642. X# if DEBUG > 0        
  643. X            printf("new %d old %d diff %d\n",
  644. X               wd.wd_boottime,
  645. X               mp->boottime,
  646. X               wd.wd_boottime - mp->boottime );
  647. X# endif /* DEBUG > 0         */
  648. X        } /* new boottime */
  649. X        } /* machine found */
  650. X        else
  651. X        mp = newmachine( wd.wd_hostname );
  652. X
  653. X        mp->latest = recvtime;    /* packet sendtime or file time */
  654. X        mp->boottime = wd.wd_boottime;
  655. X        mp->sendtime = wd.wd_sendtime;
  656. X        mp->busy[this] = FALSE;    /* clear for this pass */
  657. X    } /* machines */
  658. X
  659. X    if( down > downtime )        /* host is down? */
  660. X        continue;
  661. X
  662. X    for( we = wd.wd_we; cc > 0 ; we++, cc -= sizeof( *we ) ) {
  663. X        int tcnt;
  664. X        int washungry;
  665. X        register char *cp, *dp;
  666. X
  667. X        washungry = ISHUNGRY(we);    /* save hungry bit */
  668. X        CLEARHUNGRY(we);        /* clear to avoid bad comparisons */
  669. X
  670. X        strncpy( user, we->we_utmp.out_name, 8 );
  671. X        strcpy( host, wd.wd_hostname );
  672. X        strcpy( tty, we->we_utmp.out_line );
  673. X
  674. X        if( machines && we->we_idle <= idletime ) /* @@ Have widletime? */
  675. X        mp->busy[this] = TRUE;    /* just flag as busy */
  676. X
  677. X# if DEBUG > 10
  678. X        printf("%s@%s %s idle %d\n", user, host, tty, we->we_idle );
  679. X# endif /* DEBUG > 10 */
  680. X
  681. X        for( tp = targets, tcnt = 0; tcnt < numt; tcnt++, tp = tp->tnext ) {
  682. X        if( ((tp->type == USER && strcmp( user, tp->user ) == 0) ||
  683. X             (tp->type == LINE && strcmp( tty, tp->user ) == 0)) ) {
  684. X
  685. X            if( (dp = index( host, '.' )) != NULL )
  686. X            *dp = EOS;    /* strip domains */
  687. X
  688. X# if DEBUG > 4
  689. X# if DEBUG < 20
  690. X            printf("%s@%s %s idle %d\n",
  691. X               user, host, tty, we->we_idle );
  692. X# endif /* DEBUG < 20 */
  693. X            printf("%s %d %d\n", tp->user,
  694. X               tp->found[this].nidle,
  695. X               tp->found[this].idle );
  696. X# endif /* DEBUG > 4 */
  697. X
  698. X            if( tp->host == WILDHOST || /* wild or */
  699. X               strcmp(host, tp->host) == 0 ) { /*matching host*/
  700. X            if( we->we_idle > idletime ) {
  701. X                tp->found[this].idle++;
  702. X            }
  703. X            else {        /* not idle */
  704. X                if( tp->found[this].nidle++ == 0 && /*first*/
  705. X                   tp->found[!this].nidle == 0 ) { /*none b4*/
  706. X# if DEBUG > 0 && DEBUG < 5
  707. X                tprint( tp );
  708. X# endif /* DEBUG > 0 && DEBUG < 5 */
  709. X                /* nothing before or, imbalance */
  710. X                if( tp->found[!this].idle == 0 ||
  711. X                   TOTAL(tp,this) > TOTAL(tp,!this) )
  712. X                    now_on( user, host, tty );
  713. X                else        /* return from idleness */
  714. X                    now_active( user, host, tty );
  715. X                } /* first non idle where none before */
  716. X            } /* not idle */
  717. X            if( washungry )
  718. X                if( tp->found[this].hungry++ == 0 &&
  719. X                   tp->found[!this].hungry == 0 )
  720. X                now_hungry( user, host, tty );
  721. X            } /* matching host */
  722. X            goto nextwhoent;
  723. X        } /* matching user */
  724. X        } /* for all targets */
  725. X    nextwhoent: ;
  726. X    } /* for we */
  727. X
  728. X    } /* for each file */
  729. X   
  730. X    for( tp = targets, i = 0; i < numt; i++, tp = tp->tnext ) {
  731. X# if DEBUG > 49
  732. X    tprint( tp );
  733. X# endif /* DEBUG > 49 */
  734. X    if( tp->found[!this].nidle > 0 ) {     /* was here before */
  735. X# if DEBUG > 4 && DEBUG < 50
  736. X        tprint( tp );
  737. X# endif /* DEBUG > 4 && DEBUG < 50 */
  738. X        if( tp->found[this].nidle == 0 ) {    /* no non idle users */
  739. X# if DEBUG > 0 && DEBUG < 5
  740. X        tprint( tp );
  741. X# endif /* DEBUG > 0 && DEBUG < 5 */
  742. X        if( tp->found[this].idle > 0 &&    /* have idle users */
  743. X           TOTAL(tp,this) == TOTAL(tp,!this) ) /* no net change */
  744. X            now_idle( tp );
  745. X        else
  746. X            now_gone( tp );    /* none idle or net change. */
  747. X                    /* must have logged out */
  748. X        } /* no non-idle */
  749. X    } /* prev had non-idle */
  750. X    else if( tp->found[!this].idle > 0 ) {    /* prev idle? */
  751. X        if( TOTAL(tp,this) == 0 ) {        /* no current */
  752. X# if DEBUG > 0 && DEBUG < 5
  753. X        tprint( tp );
  754. X# endif /* DEBUG > 0 && DEBUG < 5 */
  755. X        now_gone( tp );
  756. X        } 
  757. X    } /* here but idle before */
  758. X    } /* for tp */
  759. X} /* do who */
  760. X
  761. Xnow_on( user, host, tty )
  762. X    char *user, *host, *tty;
  763. X{
  764. X    stomp();
  765. X    printf("bow wow!! %s now on %s %s\n", user, host, tty );
  766. X} /* now on */
  767. X
  768. X
  769. Xnow_active( user, host, tty )
  770. X    char *user, *host, *tty;
  771. X{
  772. X    stomp();
  773. X    printf("arf!!     %s active on %s %s\n", user, host, tty );
  774. X} /* now active */
  775. X
  776. Xnow_hungry( user, host, tty )
  777. X    char *user, *host, *tty;
  778. X{
  779. X    stomp();
  780. X    printf("woof!     %s hungry on %s %s\n",  user, host, tty );
  781. X} /* now hungry */
  782. X
  783. Xnow_idle( tp )
  784. X    struct target *tp;
  785. X{
  786. X    stomp();
  787. X    printf("Zzzz!     %s", tp->user );
  788. X    if( tp->host != WILDHOST ) {
  789. X    putchar('@');
  790. X    fputs( tp->host, stdout );
  791. X    } /* have host */
  792. X    puts(" is idle");
  793. X} /* now idle */
  794. X
  795. Xnow_gone( tp )
  796. X    struct target *tp;
  797. X{
  798. X    stomp();
  799. X    printf("Aroooo!   %s", tp->user );
  800. X    if( tp->host != WILDHOST ) {
  801. X    putchar('@');
  802. X    fputs( tp->host, stdout );
  803. X    } /* have host */
  804. X    if( tp->type == USER )
  805. X    puts(" is gone");
  806. X    else
  807. X    puts(" is free");
  808. X} /* now gone */
  809. X
  810. Xnow_sated( user, host )
  811. X    char *user, *host;
  812. X{
  813. X    stomp();
  814. X    printf("Burp!!    %s", user );
  815. X    fputs( user, stdout );
  816. X    if( host != WILDHOST ) {
  817. X    putchar('@');
  818. X    fputs( host, stdout );
  819. X    } /* have host */
  820. X    puts(" is sated!!");
  821. X} /* now sated */
  822. X
  823. Xbool
  824. Xcheckflag( buf, name, ptr )
  825. X    char *name, *buf;
  826. X    bool *ptr;
  827. X{
  828. X    int l;
  829. X
  830. X    l = strlen( name );
  831. X    if( strncmp( name, buf, l ) == 0 )
  832. X    *ptr = TRUE;
  833. X    else if( buf[0] == 'n' && buf[1] == 'o' && strncmp( name, buf+2, l ) == 0 )
  834. X    *ptr = FALSE;
  835. X    else
  836. X    return( FALSE );
  837. X    return( TRUE );
  838. X} /* checkflag */
  839. X
  840. Xbool
  841. Xnumarg( buf, name, ptr, scale )
  842. X    char *name, *buf;
  843. X    int *ptr, scale;
  844. X{
  845. X    if( strncmp( name, buf, strlen( name ) ) == 0 ) {
  846. X    int i;
  847. X    if( sscanf( buf, "%*s %d", &i ) != 1 )
  848. X        fprintf( stderr, "fido: bad line: %s\n", buf );
  849. X    else
  850. X        *ptr = i * scale;
  851. X    return( TRUE );
  852. X    } /* matches */
  853. X    return( FALSE );
  854. X} /* numarg */
  855. X
  856. Xbool
  857. Xreadinit( file )
  858. X    char *file;
  859. X{
  860. X    FILE *f;
  861. X    char buf[ MAXBUF ];
  862. X
  863. X    if( (f = fopen(file, "r")) == NULL )
  864. X    return( FALSE );
  865. X
  866. X    while( fgets(buf, sizeof buf, f) != NULL ) {
  867. X    register char *cp;
  868. X
  869. X    if( (cp = index(buf,'\n')) != NULL )
  870. X        *cp = EOS;
  871. X
  872. X# if DEBUG > 9
  873. X    printf("%s\n", buf );
  874. X# endif /* DEBUG > 9 */
  875. X
  876. X    if( buf[0] == '#' )
  877. X        continue;
  878. X
  879. X    if( strncmp(buf, "user", 4) == 0 )
  880. X        r_user( buf );
  881. X    else if( strncmp(buf, "line", 4) == 0 )
  882. X        r_line( buf );
  883. X    else if( strncmp( buf, "watch", 5 ) == 0 )
  884. X        r_watch( buf );
  885. X    else if( checkflag(buf, "beep", &beep) )
  886. X        continue;
  887. X    else if( checkflag(buf, "machines", &machines) )
  888. X        continue;
  889. X    else if( checkflag(buf, "stamp", &stamp) )
  890. X        continue;
  891. X    else if( numarg(buf, "idletime", &idletime, 60)  )
  892. X        continue;
  893. X    else if( numarg(buf, "downtime", &downtime, 60)  )
  894. X        continue;
  895. X    else if( numarg(buf, "sleeptime", &sleeptime, 1) )
  896. X        continue;
  897. X    else if( numarg(buf, "tick", &tick, 1) )
  898. X        continue;
  899. X    else
  900. X        fprintf(stderr, "fido: bad line in %s: %s\n", file, buf );
  901. X    } /* while */
  902. X    if( sleeptime < MIN_SLEEPTIME )
  903. X    sleeptime = MIN_SLEEPTIME;
  904. X    if( tick > MAX_TICK )
  905. X    tick = MAX_TICK;
  906. X    fclose ( f );
  907. X    return( TRUE );
  908. X} /* readinit */
  909. X
  910. Xchar *savestr( s )
  911. Xregister char *s;
  912. X{
  913. X    register char *t;
  914. X
  915. X    while( *s != EOS && isspace( *s ) )
  916. X    s++;
  917. X    if( *s == EOS )
  918. X    return( "" );
  919. X
  920. X    t = s;
  921. X    while( *t != EOS && !isspace( *t ) && *t != '\n' )
  922. X    t++;
  923. X    if( *t != EOS )
  924. X    *t = EOS;
  925. X
  926. X# if DEBUG > 10
  927. X    printf("savestr '%s'\n", s);
  928. X# endif /* DEBUG > 10 */
  929. X
  930. X    return( strcpy( malloc( strlen( s ) + 1 ), s) );
  931. X    
  932. X} /* savestr */
  933. X
  934. Xr_user( l )
  935. Xregister char *l;
  936. X{
  937. X    register char *cp;
  938. X    register struct target *tp;
  939. X
  940. X    while( *l != EOS && !isspace( *l ) )
  941. X      l++;
  942. X
  943. X    while( *l != EOS && isspace( *l ) )
  944. X      l++;
  945. X
  946. X    if( *l == EOS )
  947. X    return;
  948. X
  949. X    tp = (struct target *) malloc( sizeof( struct target ) );
  950. X    tp->type = USER;
  951. X    tp->tnext = NULL;
  952. X
  953. X    if( targets == NULL )
  954. X    targets = tp;
  955. X    else
  956. X    lasttarget->tnext = tp;
  957. X
  958. X    if( (cp = index(l, '@')) != NULL ) {
  959. X    *cp++ = EOS;            /* blast @ to tie off user */
  960. X    if( *cp != EOS )        /* have host? */
  961. X        tp->host = savestr( cp );    /* save it */
  962. X    else                /* none? */
  963. X        tp->host = WILDHOST;    /* WTF -- save as wild */
  964. X    } /* have host */
  965. X    else
  966. X    tp->host = WILDHOST;
  967. X    tp->user = savestr( l );
  968. X
  969. X    tp->found[0].idle   = 0;
  970. X    tp->found[0].nidle  = 0;
  971. X    tp->found[0].hungry = 0;
  972. X    tp->found[1].idle   = 0;
  973. X    tp->found[1].nidle  = 0;
  974. X    tp->found[1].hungry = 0;
  975. X
  976. X    lasttarget = tp;
  977. X    numt++;
  978. X} /* r_user */
  979. X
  980. Xr_line( l )
  981. Xregister char *l;
  982. X{
  983. X    register char *cp;
  984. X    register struct target *tp;
  985. X
  986. X    while( *l != EOS && !isspace( *l ) )
  987. X      l++;
  988. X
  989. X    while( *l != EOS && isspace( *l ) )
  990. X      l++;
  991. X
  992. X    if( *l == EOS )
  993. X    return;
  994. X
  995. X    tp = (struct target *) malloc( sizeof( struct target ) );
  996. X    tp->type = LINE;
  997. X    tp->tnext = NULL;
  998. X
  999. X    if( targets == NULL )
  1000. X    targets = tp;
  1001. X    else
  1002. X    lasttarget->tnext = tp;
  1003. X
  1004. X    if( (cp = index(l, '@')) != NULL ) {
  1005. X    *cp++ = EOS;            /* blast @ to tie off user */
  1006. X    if( *cp != EOS )        /* have host? */
  1007. X        tp->host = savestr( cp );    /* save it */
  1008. X    else                /* none? */
  1009. X        tp->host = WILDHOST;    /* WTF -- save as wild */
  1010. X    } /* have host */
  1011. X    else
  1012. X    tp->host = WILDHOST;
  1013. X    tp->user = savestr( l );
  1014. X
  1015. X    tp->found[0].idle   = 0;
  1016. X    tp->found[0].nidle  = 0;
  1017. X    tp->found[0].hungry = 0;
  1018. X    tp->found[1].idle   = 0;
  1019. X    tp->found[1].nidle  = 0;
  1020. X    tp->found[1].hungry = 0;
  1021. X
  1022. X    lasttarget = tp;
  1023. X    numt++;
  1024. X} /* r_line */
  1025. X
  1026. Xr_watch( l )
  1027. Xregister char *l;
  1028. X{
  1029. X    register struct machine *mp;
  1030. X
  1031. X    while( *l != EOS && !isspace( *l ) )
  1032. X      l++;
  1033. X
  1034. X    while( *l != EOS && isspace( *l ) )
  1035. X      l++;
  1036. X
  1037. X    if( *l == EOS ) {
  1038. X    watchall = TRUE;
  1039. X    return;
  1040. X    }
  1041. X    if( (mp = findmachine( l )) == NULL )
  1042. X    mp = newmachine( l );
  1043. X    mp->watch = TRUE;
  1044. X} /* r_watch */
  1045. X
  1046. Xdumpinit() {
  1047. X    register int i;
  1048. X    register struct target *tp;
  1049. X
  1050. X    for( tp = targets, i = 0; i < numt; tp = tp->tnext, i++ )
  1051. X    if( tp->host == WILDHOST )
  1052. X        printf("%s\n", tp->user );
  1053. X    else
  1054. X        printf("%s@%s\n", tp->user, tp->host );
  1055. X} /* dumpinit */
  1056. X
  1057. X/**************** machine stuff ****************/
  1058. X
  1059. X# define SH 7
  1060. X# define WS 32
  1061. Xunsigned
  1062. Xhash( n )
  1063. X    register char *n;
  1064. X{
  1065. X    register unsigned l, i;
  1066. X
  1067. X    i = l = 0;
  1068. X    while( *n != EOS ) {
  1069. X    i = ((i << SH) | (i >> (WS-SH))) ^ *n++;
  1070. X    l++;
  1071. X    } /* while not end of string */
  1072. X    return( (i + l) % MHSIZE );
  1073. X} /* hash */
  1074. X
  1075. Xstruct machine *
  1076. Xfindmachine( n )
  1077. X    char *n;
  1078. X{
  1079. X    int h;
  1080. X    register struct machine *mp;
  1081. X
  1082. X    h = hash( n );
  1083. X    for( mp = mhash[ h ]; mp != NULL; mp = mp->hnext )
  1084. X    if( strcmp(mp->name, n) == 0 )
  1085. X        return( mp );
  1086. X    return( NULL );
  1087. X} /* findmachine */
  1088. X
  1089. Xstruct machine *
  1090. Xnewmachine( n )
  1091. X    char *n;
  1092. X{
  1093. X    int h;
  1094. X    register struct machine *mp;
  1095. X
  1096. X    mp = (struct machine *) malloc( sizeof( struct machine ) );
  1097. X    strcpy(mp->name, n);
  1098. X    mp->state = UP;
  1099. X
  1100. X    mp->watch = watchall;        /* get default */
  1101. X    mp->busy[0] = mp->busy[1] = TRUE;    /* assume is/was busy */
  1102. X
  1103. X    mp->anext = allmachines;        /* put in list of all machines */
  1104. X    allmachines = mp;
  1105. X
  1106. X    h = hash( n );            /* put in hash list */
  1107. X    mp->hnext = mhash[ h ];
  1108. X    mhash[ h ] = mp;
  1109. X
  1110. X    if( !firstpass )
  1111. X    saynew( mp );
  1112. X
  1113. X    return( mp );
  1114. X} /* newmachine */
  1115. X
  1116. X
  1117. Xscan() {
  1118. X    register struct machine *mp;
  1119. X    int i;
  1120. X    static char *TF = "FT";
  1121. X
  1122. X    for( mp = allmachines; mp != NULL; mp = mp->anext ) {
  1123. X# if DEBUG > 4
  1124. X    printf("scan: %s (%s) busy: %c (%c)\n",
  1125. X           mp->name, state_name[ (int)mp->state ],
  1126. X           TF[mp->busy[this]], TF[mp->busy[!this]] );
  1127. X# endif /* DEBUG > 4 */
  1128. X    /*
  1129. X     * if /usr/spool/rwho is on another machine (via NFS) and our
  1130. X     * clocks differ we get alot of up/down noise.  there must be
  1131. X     * a good way to dampen this!!
  1132. X     *
  1133. X     * perhaps keep a new state "may be down" as a cushion
  1134. X     *
  1135. X     * perhaps keep threshold per host and increase each time the
  1136. X     * host comes "up" without having been rebooted (see
  1137. X     * wd_boottime)
  1138. X     */
  1139. X    if( now - mp->latest > downtime ) { /* is down */
  1140. X        if( mp->state != DOWN ) {
  1141. X        saydown( mp, now - mp->latest );
  1142. X        mp->state = DOWN;
  1143. X        } /* new state: down */
  1144. X    } /* is down */
  1145. X    else {            /* is up */
  1146. X        if( mp->state != UP ) {
  1147. X        sayup( mp );
  1148. X        mp->state = UP;
  1149. X        } /* new state: up */
  1150. X
  1151. X        if(
  1152. X# if DEBUG <= 5
  1153. X           !firstpass &&
  1154. X# endif /* DEBUG <= 5 */
  1155. X           mp->watch && (mp->busy[this] ^ mp->busy[!this]) )
  1156. X        if( mp->busy[this] )
  1157. X            mach_busy( mp );
  1158. X        else
  1159. X            mach_free( mp );
  1160. X    } /* is up */
  1161. X    } /* for all machines */
  1162. X    firstpass = FALSE;
  1163. X} /* scan */
  1164. X
  1165. Xstomp() {
  1166. X    if( beep )
  1167. X    putchar('\007');
  1168. X    if( stamp )
  1169. X    fputs( nowstr, stdout );
  1170. X} /* stomp */
  1171. X
  1172. Xsaydown( mp, t )
  1173. X    struct machine *mp;
  1174. X    int t;
  1175. X{
  1176. X    int d, h, m;
  1177. X
  1178. X    stomp();
  1179. X    printf("<<<<<<<<< %s is down (", mp->name );
  1180. X    t /= 60;                /* toss seconds */
  1181. X
  1182. X    m = t % 60;                /* get mins */
  1183. X    t /= 60;                /* toss mins */
  1184. X
  1185. X    h = t % 24;                /* get hours */
  1186. X    t /= 24;                /* toss hours */
  1187. X
  1188. X    d = t % 7;                /* get days */
  1189. X    t /= 7;                /* cast out days (leave weeks) */
  1190. X
  1191. X    if( t > 0 ) printf("%dw", t );
  1192. X    if( d > 0 ) printf("%dd", d );
  1193. X    if( h > 0 ) printf("%dh", h );
  1194. X    if( m > 0 ) printf("%dm", m );
  1195. X    puts(")");
  1196. X} /* saydown */
  1197. X
  1198. Xsayup( mp )
  1199. X    struct machine *mp;
  1200. X{
  1201. X    stomp();
  1202. X    printf(">>>>>>>>> %s is up\n", mp->name );
  1203. X} /* sayup */
  1204. X
  1205. Xsaynew( mp )
  1206. X    struct machine *mp;
  1207. X{
  1208. X    stomp();
  1209. X    printf("!!!!!!!!! new machine %s\n", mp->name );
  1210. X} /* saynew sayme */
  1211. X
  1212. Xsayreboot( mp )
  1213. X    struct machine *mp;
  1214. X{
  1215. X    stomp();
  1216. X    printf("~~~~~~~~~ %s rebooted\n", mp->name );
  1217. X} /* sayreboot */
  1218. X
  1219. Xmach_free( mp )
  1220. X    struct machine *mp;
  1221. X{
  1222. X    stomp();
  1223. X    printf("+++++++++ %s is free\n", mp->name );
  1224. X} /* machine idle */
  1225. X
  1226. Xmach_busy( mp )
  1227. X    struct machine *mp;
  1228. X{
  1229. X    stomp();
  1230. X    printf("--------- %s is busy\n", mp->name );
  1231. X} /* machine busy */
  1232. X
  1233. X# if DEBUG > 0
  1234. Xtprint( tp )
  1235. X    register struct target *tp;
  1236. X{
  1237. X    printf("%s (%d %d %d) (%d %d %d)",
  1238. X       tp->user,
  1239. X
  1240. X       tp->found[this].nidle,
  1241. X       tp->found[this].idle,
  1242. X       tp->found[this].hungry,
  1243. X
  1244. X       tp->found[!this].nidle,
  1245. X       tp->found[!this].idle,
  1246. X       tp->found[!this].hungry
  1247. X       );
  1248. X    if( TOTAL(tp,this) != TOTAL(tp,!this) )
  1249. X    printf(" ***** net imbalance!! *****");
  1250. X    puts("");
  1251. X} /* tprint */
  1252. X# endif /* DEBUG > 0 */
  1253. END_OF_fido.c
  1254. if test 23530 -ne `wc -c <fido.c`; then
  1255.     echo shar: \"fido.c\" unpacked with wrong size!
  1256. fi
  1257. # end of overwriting check
  1258. fi
  1259. echo shar: End of archive 1 \(of 1\).
  1260. cp /dev/null ark1isdone
  1261. MISSING=""
  1262. for I in 1 ; do
  1263.     if test ! -f ark${I}isdone ; then
  1264.     MISSING="${MISSING} ${I}"
  1265.     fi
  1266. done
  1267. if test "${MISSING}" = "" ; then
  1268.     echo You have unpacked all 1 archives.
  1269.     rm -f ark[1-9]isdone
  1270. else
  1271.     echo You still need to unpack the following archives:
  1272.     echo "        " ${MISSING}
  1273. fi
  1274. ##  End of shell archive.
  1275. exit 0
  1276.